fix: prevent UI freezes from blocking SSH calls on remote branches#401
fix: prevent UI freezes from blocking SSH calls on remote branches#401
Conversation
Add a traced invoke wrapper (src/lib/invoke.ts) that logs every IPC call to the Rust backend at info level with the command name and wall-clock duration. Calls exceeding 100ms are tagged [SLOW] to make it easy to spot UI-blocking operations when debugging remote project freezes. All four files that previously imported invoke directly from @tauri-apps/api/core now use the wrapper: - src/lib/commands.ts (main command wrappers) - src/lib/features/actions/actions.ts (action commands) - src/lib/features/branches/branch.ts (branch opener commands) - src/lib/shared/persistentStore.ts (preferences store path)
The has_unpushed_commits command was the primary cause of UI freezes for remote projects. It was called from three frontend sites and each remote call did 2 synchronous SSH round-trips (~5s each) via blox ws_exec. Call frequency: 10 calls in the first 25 seconds of app usage — fired for every branch whenever branchesByProject changed (12 reassignment sites in ProjectHome). Local branches completed in 39–202ms, but remote branches blocked for 4.5–4.8s each. Backend (prs.rs): - Convert has_unpushed_commits from sync pub fn to async, wrapping the remote-branch SSH path in spawn_blocking so it no longer blocks the Tauri IPC thread. Frontend — skip the check entirely for remote branches: - ProjectHome.svelte safeToDeleteProjects $effect: treat merged remote branches as safe without the SSH check. - ProjectHome.svelte handleDeleteProjectRequest: same skip. - BranchCardPrButton.svelte: skip the per-timeline-refresh unpushed check for remote branches (isLocal guard).
The $effect that computes safeToDeleteProjects was iterating over all
projects, calling hasUnpushedCommits for every branch across every
project — even ones not currently displayed. Since the result is only
consumed in the {#each visibleProjects} render loop, this was wasted
work (and especially costly for remote branches with ~5s SSH
round-trips).
Narrow the loop from `projects` to `visibleProjects` so the check
only runs for the project the user is currently viewing.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ddee15bd5f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| branches.map(async (branch) => { | ||
| const isMerged = branch.prState === 'MERGED'; | ||
| if (!isMerged) return false; | ||
| if (branch.branchType === 'remote') return true; |
There was a problem hiding this comment.
Preserve unpushed check before auto-deleting remote projects
Returning true for every merged remote branch here can incorrectly classify a project as safe to delete even when the remote workspace still has commits that were never pushed. start_repo_session with expected_outcome="commit" only creates commits (it does not require a push), so this state is reachable; then delete_project eventually calls remote cleanup that deletes the workspace (cleanup_branch_resources -> blox::ws_delete), which can drop those commits without showing the confirmation path.
Useful? React with 👍 / 👎.
apps/staged/src/lib/invoke.ts
Outdated
| const result = await tauriInvoke<T>(cmd, args); | ||
| const elapsed = performance.now() - start; | ||
| const tag = elapsed >= SLOW_THRESHOLD_MS ? ' [SLOW]' : ''; | ||
| info(`[invoke]${tag} ${cmd} completed in ${elapsed.toFixed(1)}ms`); |
There was a problem hiding this comment.
Guard IPC timing logs when plugin-log is unavailable
This wrapper logs every invoke call via @tauri-apps/plugin-log, but the backend only installs tauri_plugin_log in debug builds (src-tauri/src/lib.rs under if cfg!(debug_assertions)), so release builds have no log plugin handler. Calling info(...) unconditionally here will generate rejected log calls for every IPC request, creating unhandled promise noise in production and potentially tripping global rejection handlers.
Useful? React with 👍 / 👎.
Delete src/lib/invoke.ts and revert all four import sites back to importing invoke directly from @tauri-apps/api/core. The timing/logging wrapper was added to diagnose the remote branch UI freeze and is no longer needed now that the fix is in place.
Summary
Fix UI freezes caused by
has_unpushed_commitsmaking blocking SSH round-trips (~5s each) on the Tauri IPC thread for remote branches.Changes
has_unpushed_commitsasync and run the blocking SSH/git calls viaspawn_blockingso they no longer freeze the UI.hasUnpushedCommitscall inBranchCardPrButtonandProjectHomefor remote branches.safeToDeleteProjectswas iterating over all projects; now it only checks the ones currently rendered, avoiding wasted IPC round-trips.